<?php
  define('SSH_VERSION', '0.1');
  
  define('SSH_SMSG_PUBLIC_KEY', 2);
  
  define('SSH_SMSG_SUCCESS', 14);
  
  define('SSH_SMSG_FAILURE', 15);
  
  define('SSH_CMSG_SESSION_KEY', 3);
  
  define('SSH_CMSG_USER', 4);
  
  define('SSH_CMSG_AUTH_PASSWORD', 9);
  
  define('SSH_CMSG_REQUEST_PTY', 10);
  
  define('SSH_CMSG_EXEC_SHELL', 12);
  
  define('SSH_CMSG_STDIN_DATA', 16);
  
  define('SSH_CMSG_EOF', 19);
  
  define('SSH_SMSG_STDOUT_DATA', 17);
  
  define('SSH_SMSG_STDERR_DATA', 18);
  
  define('SSH_SMSG_EXITSTATUS', 20);
  
  define('SSH_CIPHER_3DES', 3);
  
  class Core_SSH_Explorer{
  	/**
  	 * host para qual devermos nos conectar.
  	 * 
  	 * @var String.
  	 */
  	private $host;
  	
  	/**
  	 * porta que devemos usar.
  	 * 
  	 * @var integer
  	 */
  	private $port;
  	
  	/**
  	 * usuário que precisamos utilizar
  	 * 
  	 * @var String
  	 */
  	private $user;
    
  	/**
  	 * senha que precisamos usar.
  	 * 
  	 * @var String
  	 */
    private $password;
    
    /**
  	 * Arquivo descriptor de nossa conexão.
  	 * 
  	 * @var integer
  	 */
  	private $file_descriptor;
  	
  	private $ssh_version;
    
  	private $ssh_server;
  	
  	/**
  	 * Criação de pacotes.
  	 * 
  	 * @var SSH_packet_forge
  	 */
  	private $packet_forge;
  	
  	/**
     * Destribuição de pacotes.
     *
     * @var SSH_packet_disolver
     */ 
    private $packet_disolver;
    
    private $session_id;

    private $session_key;
    
    private $logged = false;
    
    private $inbuffer, $outbuffer;
    
    private $con = null;
    
    private $shell_type = 'xterm';
    
    private $shell = null;
    
  	/**
  	 * Método construct
  	 * @param string $host       - IP ou dominio do servidor
  	 * @param integer $port      - porta de acesso(padrão 22)
  	 */
  	public function __construct($host = "", $port = 22){
  		if($host){
  			$this->host = $host;
  		}
  		else{
  			throw new Core_SSH_Exception("HOST NÃO PODE SER VAZIO.");
  		}
  		
  	  if($port){
        $this->port = $port;
      }
      else{
      	throw new Core_SSH_Exception("PORTA NÃO PODE SER VAZIA.");
      }
  	}
  	
  	public function connect($username, $password){
  		$this->file_descriptor = fsockopen($this->host, $this->port, $errno, $errstr);
  		  
  		if(!$this->file_descriptor){
  			throw new Core_SSH_Exception($errstr, $errno);
  		}
  		
  		if($username){
  		  $this->user = $username;
  		}
  		else{
  			throw new Core_SSH_Exception("USERNAME NÃO PODE SER VAZIO.");
  		}
  		
  		if($password){
  		  $this->password = $password;
  		}  
  		else{
  		  throw new Core_SSH_Exception("SENHA NÃO PODE SER VAZIA.");
  		}
  		
  		$this->packet_forge = new Core_SSH_PacketForge($this->file_descriptor);
  		
  		$this->packet_disolver = new Core_SSH_PacketDisolver($this->file_descriptor);
  		
  	  $this->connectGetPeerInfo();
    
  	  $this->connectSendOurInfo();
    
  	  $this->exPubKey();
      
      try{
        $this->login();
      } 
      catch(Exception $e){
        $this->disconnect();
      
        throw new Core_SSH_Exception('NÃO É POSSÍVEL AUTENTICAR - DESCONECTANDO!');
      }
      
	    try{
	      $this->reqPtyAndShell();
	    } 
	    catch(Exception $e){
	      $this->disconnect();
	      
	      throw new Core_SSH_Exception($e->getMessage());
	    }
	    
      /**
      * definir o modo nonblocking para leitura ilimitada
      */
      $this->setNonBlocking();
    }

	  private function setNonBlocking(){
	    socket_set_blocking($this->file_descriptor, false);
	  }

	  private function readUpdateInBuff(){
	    $this->packet_disolver->readPacket();
	    
	    if(($this->packet_disolver->getPacketType() == SSH_SMSG_STDOUT_DATA) || ($this->packet_disolver->getPacketType() == SSH_SMSG_STDOUT_DATA)){
	      $newdata = $this->packet_disolver->getData();
	      
	      if(strlen($newdata) > 0){
	        $str_len = Core_SSH_Functions::dwordToint(substr($newdata, 0, 4));
	        
	        $this->inbuffer .= substr($newdata, 4, $str_len);
	      }
	    } 
	    else{
	      throw new Core_SSH_Exception('TIPO DE PACOTE DE DADOS NÃO ENCONTRADO: ' . $this->packet_disolver->getPacketType());
	    }
	  }

    public function read($max_len = null){
      if(!feof($this->file_descriptor)){
        if($this->logged){
          $this->readUpdateInBuff();
          
          if($max_len != null && $max_len < strlen($this->inbuffer)){
            $ret = substr($this->inbuffer, 0, $max_len);
          
            $this->inbuffer = substr($this->inbuffer, $max_len, strlen($this->inbuffer) - $max_len);
          } 
          else{
            $ret = $this->inbuffer;
            
            $this->inbuffer = "";
          }
          
          return $ret;
        } 
        else{
          throw new Core_SSH_Exception('Você precisa fazer o login antes de ler e gravar dados');
        }
      } 
      else{
        throw new Core_SSH_Exception('Desconectado!');
      }
    }
	
    public function write($data){
	    if(!feof($this->fd)){
	      if($this->logged){
	        $content = chr(SSH_CMSG_STDIN_DATA);
	        
	        $content.= integer2dword(strlen($data));
	        
	        $content.= $data;
	        
	        $this->packet_forge->fillData($content);
	        
	        $this->packet_forge->buildPacket();
	      } 
	      else{
	        throw new SSHException('Você precisa fazer o login antes de ler e gravar dados');
	      }
	    } 
	    else{
	      throw new SSHException('Desconectado!');
	    }
	  }

	  private function reqPtyAndShell(){
	    /**
	     * Requesição pty vt100 80x24, que não possui suporte
	     */
	    $content = chr(SSH_CMSG_REQUEST_PTY);
	    
	    $term_type = "vt100";
	    
	    $content.= Core_SSH_Functions::intToDword(strlen($term_type));
	    $content.= $term_type;
	    $content.= Core_SSH_Functions::intToDword(24); // height
	    $content.= Core_SSH_Functions::intToDword(80); // width
	    $content.= Core_SSH_Functions::intToDword(0); // graph. height
	    $content.= Core_SSH_Functions::intToDword(0); // graph. width
	    $content.= chr(0); // terminal modes, we support nothing, we pass end of args immediatly
	    
	    $this->packet_forge->fillData($content);
	    
	    $this->packet_forge->buildPacket();
	
	    $this->packet_disolver->readPacket();
	    
	    if($this->packet_disolver->getPacketType() != SSH_SMSG_SUCCESS){
	      throw new Exception('Não é possível alocar pty - desconectando!');
	    }
	
	    /**
	     * Requisição do Shell interativo
	     */
	    $content = chr(SSH_CMSG_EXEC_SHELL);
	    
	    $this->packet_forge->fillData($content);
	    
	    $this->packet_forge->buildPacket();
	    
	    if($this->packet_disolver->getPacketType() != SSH_SMSG_SUCCESS){
	      throw new Exception('Não é possível iniciar o shell interativo - desconectar!');
	    }
	  }

	  private function login(){
	    $content = chr(SSH_CMSG_USER);
	    
	    $content.= integer2dword(strlen($this->login));
	    
	    $content.= $this->login;
	    
	    $this->packet_forge->fillData($content);
	    
	    $this->packet_forge->buildPacket();
	
	    $this->packet_disolver->readPacket();
	    
	    if($this->packet_disolver->getPacketType() == SSH_SMSG_SUCCESS){
	      echo "login OK"; flush();
	    } 
	    elseif($this->packet_disolver->getPacketType() == SSH_SMSG_FAILURE){
	      /**
	       * O usuário pode estar OK, mas precisa de Senha
	       * Tente enviar a senha.
	       */
	      $content = chr(SSH_CMSG_AUTH_PASSWORD);
	      
	      $content.= Core_SSH_Functions::intToDword(strlen($this->passwd));
	      
	      $content.= $this->passwd;
	      
	      $this->packet_forge->fillData($content);
	      
	      $this->packet_forge->buildPacket();
	
	      /**
	       * Leitura e resposta.
	       */
	      $this->packet_disolver->readPacket();
	      
	      if($this->packet_disolver->getPacketType() == SSH_SMSG_SUCCESS){
	        echo"login OK\n";
	        
	        $this->logged = true;
	        
	        $this->inbuffer = $this->outbuffer = "";
	      } 
	      else{
	        throw new Exception('Senha inválida');
	      }
	    } 
	    else{
	      throw new Exception("Resposta inválida, Aguardando SSH_MSG_SUCCESS ou SSH_SMSG_FAILURE");
	    }
	  }

    /**
    * recv server and host public keys
    * exchange session key
    * begin encryption
    */
    private function exPubKey(){
      $this->packet_disolver->readPacket();
      
      if($this->packet_disolver->getPacketType() != SSH_SMSG_PUBLIC_KEY){
        throw new Core_SSH_Exception('Aguardando SSH_SMSG_PUBLIC_KEY!');
      }
	    
	    /**
	     * 8 bytes      anti_spoofing_cookie  
	     * 32-bit int   server_key_bits  
	     * mp-int       server_key_public_exponent  
	     * mp-int       server_key_public_modulus  
	     * 32-bit int   host_key_bits  
	     * mp-int       host_key_public_exponent  
	     * mp-int       host_key_public_modulus  
	     * 32-bit int   protocol_flags  
	     * 32-bit int   supported_ciphers_mask  
	     * 32-bit int   supported_authentications_mask  
	    */
      $content = $this->packet_disolver->getData();
      
	    $pos = 0;
	    
	    $cookie = substr($content, $pos, 8); 
	    
	    $pos += 8;
	
	    $server_key_bits = Core_SSH_Functions::dwordToInt(substr($content, $pos, 4)); 
	    
	    $pos += 4;
	    
	    $server_key_public_exponent = Core_SSH_Functions::mpIntRead($content, $pos);
	    
	    $server_key_public_modulus = Core_SSH_Functions::mpIntRead($content, $pos);
	
	    $host_key_bits = Core_SSH_Functions::dwordToInt(substr($content, $pos, 4)); 
	    
	    $pos += 4;
	    
	    $host_key_public_exponent = Core_SSH_Functions::mpIntRead($content, $pos);
	    
	    $host_key_public_modulus = Core_SSH_Functions::mpIntRead($content, $pos);
	
	    $protocol_flags = Core_SSH_Functions::dwordToInt(substr($content, $pos, 4)); 
	    
	    $pos += 4;
	    
	    $supported_ciphers_mask = Core_SSH_Functions::dwordToInt(substr($content,$pos,4)); 
	    
	    $pos += 4;
	    
	    $supported_authentications_mask = Core_SSH_Functions::dwordToInt(substr($content, $pos, 4)); 
	    
	    $pos += 4;

	    /**
	     * compute session_id
	     */
	    $host_key = Core_SSH_Functions::intToBlob($host_key_public_modulus);
	    
	    $server_key = Core_SSH_Functions::intToBlob($server_key_public_modulus);
	    
	    $session_id = md5($host_key . $server_key . $cookie, true);
	    
	    $this->session_id = $session_id;
	
	    /**
	     * generate session key
	     * The key is a 256 bit
	     * random number, interpreted as a 32-byte key, with the least
	     * significant 8 bits being the first byte of the key
	     */
	    $rnd = "";
	    
	    for($i = 0; $i < 32; $i++){
	      $rnd .= chr(rand(0,255));
	    }
	    
	    $this->session_key = $rnd;
	    /*
	    * According to the protocol spec, the first byte of the session key
	    * is the highest byte of the integer.  The session key is xored with
	    * the first 16 bytes of the session id.
	    */
	    $key = $this->session_key;
	    
	    for($i = 0; $i < 16; $i++){
	      $key[$i] = $key[$i] ^ $this->session_id[$i];
	    }
	     
	    /*
	    * Encrypt the integer using the public key and host key of the
	    * server(key with smaller modulus first).
	    */
	    if(bccomp($server_key_public_modulus, $host_key_public_modulus) < 0){
	      /**
	       * host key has larger modulus
	       */
	      $crypted = Core_SSH_Functions::rsaCrypt(Core_SSH_Functions::blobToInt($key), $server_key_public_exponent, $server_key_public_modulus);
	      
	      $crypted = Core_SSH_Functions::rsaCrypt($crypted, $host_key_public_exponent, $host_key_public_modulus);
	    } 
	    else{
	      /**
	       * host key has smaller modulus
	       */
	      $crypted = Core_SSH_Functions::rsaCrypt(Core_SSH_Functions::blobToInt($key), $host_key_public_exponent, $host_key_public_modulus);
	      $crypted = Core_SSH_Functions::rsaCrypt($crypted, $server_key_public_exponent, $server_key_public_modulus);
	    }
	
	    /**
	     * reply with session key
	     *
	     * 1 byte       cipher_type  
	     * 8 bytes      anti_spoofing_cookie  
	     * mp-int       double encrypted session key  
	     * 32-bit int   protocol_flags 
	     */
	    $data = chr(SSH_CMSG_SESSION_KEY);
	    $data.= chr(SSH_CIPHER_3DES);
	    $data.= $cookie;
	    
	    /**
	     * double encrypted session_key
	     */
	    $data .= Core_SSH_Functions::mpIntCreate($crypted);
	
	    $data .= Core_SSH_Functions::intToDword(0); // Sinalizações de protocolo - Não há suporte a quaisquer recursos adicionais ;))
	
	    $this->packet_forge->fillData($data);
	    
	    $this->packet_forge->buildPacket();
	
	    /**
	      * CRYPTOOOO
	      */
	    $tri = new Core_SSH_TripleDes();
	    $tri->setKeys($this->session_key);
	
	    $this->packet_disolver->setKey($this->session_key, $tri);
	    
	    $this->packet_disolver->readPacket();
	
	    $this->packet_forge->seKey($this->session_key, $tri);
	
	    if($this->packet_disolver->getPacketType() == SSH_SMSG_SUCCESS){
	      echo "Mecanismos de cryptografia iniciado... Vamos trabalhar!!!\n"; 
	      
	      flush();
	    } 
	    else{
	      throw new Exception("FATAL: Algo muito sério está acontecendo com o motor de criptografia.! Finalizando... Finaliza, Final, Fim...");
	    }
	  }

	  private function connectSendOurInfo(){
	    /**
	     * Se a conexão foi estabelecida.
	     */
	    if(!feof($this->file_descriptor)){
	      fputs($this->file_descriptor, "SSH-1.5-phpSSH_" . SSH_VERSION . "\r\n", 255);
	    } 
	    else{
	      throw new Core_SSH_Exception('Conexão não realizada.!');
	    }
	  }
	  /**
	   * gets peer information like ssh version, protocol version etc
	   *
	   */
	  private function connectGetPeerInfo(){
	    /**
	     * if connection is estabilished
	     */
	    if(!feof($this->file_descriptor)){
	      /**
	       * read initial line
	       */
	      $init_line = fgets($this->file_descriptor, 255);
	      /**
	       * and parse it
	       */
	      if(!ereg("^SSH\-([0-9\.]+)\-([[:print:]]+)", $init_line, $parts)){
	        throw new Core_SSH_Exception('Não existe um servidor SSH para conexão.');
	      }
	      /**
	       * fill in server info variables
	       */
	      $this->ssh_version = $parts[1];
	      
	      $this->ssh_server = $parts[2];
	      
	      /**
	       * and check whether we support this server
	       */
	      
	      //if(substr($this->ssh_version, 0, 1) != "1"){
	       //throw new Core_SSH_Exception("SSH versão {$this->ssh_version} não é suportado!");
	      //}
	    } 
	    else{
	      throw new Core_SSH_Exception('Não conectado!');
	    }
	  }
	
	  /**
	   * close connection
	   *
	   */
	  public function disconnect(){
	    if($this->file_descriptor){
	      if($this->logged){
	        $this->packet_forge->fillData(chr(SSH_CMSG_EOF));
	        
	        $this->packet_forge->buildPacket();
	      } 
	      else{
	        throw new Core_SSH_Exception('Você precisa fazer o login antes de ler e gravar dados');
	      }
	      fclose($this->fd);
	    } 
	    else{
	      throw new Core_SSH_Exception('Você não pode fechar abrir conexão');
	    }
	  }

	  public function getServerSshVersion(){
	    return $this->ssh_version;
	  }
	
	  public function getServerSoft(){
	    return $this->ssh_server;
	  }
  		
    
	  
	  
	  public function authPassword($user = '', $password = ''){
      if($user != ''){
     	  $this->user  = $user;
      }
      
      if($password != ''){
     	  $this->password  = $password; 
      }
      
      if(!ssh2_auth_password($this->con, $this->user, $this->password)){
        $this->log .= "Authorization failed !";
      }
    }

    public function openShell($shell_type = ''){
    	if($shell_type != ''){
    	  $this->shell_type = $shell_type;	
    	}
    
    	$this->shell = ssh2_shell( $this->con,  $this->shell_type );
    
    	if(!$this->shell){
    	  $this->log .= " Shell connection failed !";
    	}
    }
    
    public function writeShell($command = ''){
    	fwrite($this->shell, $command."\n");
    }

    public function cmdExec(){
    	$argc = func_num_args();
    	
    	$argv = func_get_args();
    	
    	$cmd = '';
    	
    	for($i=0; $i < $argc ; $i++){
        if($i !=($argc-1)){
          $cmd .= $argv[$i]." && ";
        }
        else{
          $cmd .= $argv[$i];
        }
    	}
    	
    	echo $cmd;
    	
    	$stream = ssh2_exec($this->con, $cmd);
    
    	stream_set_blocking( $stream, true );
    
    	return fread( $stream, 4096 );
    }
    
    public function getLog(){
    	return $this->log;
    }
  }